﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Html;
using System.Web.Routing;

namespace Quintsys.Core.Extensions
{
    public static class HtmlExtensions
    {
        /// <summary>
        ///     Renders an image tag.
        ///     Also provides an additional parameter to specify the alternate (alt) text.
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="id"></param>
        /// <param name="url"></param>
        /// <param name="alternateText"></param>
        /// <param name="htmlAttributes"></param>
        /// <returns></returns>
        public static MvcHtmlString Image(this HtmlHelper helper, string id, string url, string alternateText,
                                          object htmlAttributes = null)
        {
            // Instantiate a UrlHelper 
            var urlHelper = new UrlHelper(helper.ViewContext.RequestContext);

            // Create tag builder
            var builder = new TagBuilder("img");

            // Create valid id
            if (!string.IsNullOrEmpty(id))
                builder.GenerateId(id);

            // Add attributes
            builder.MergeAttribute("src", urlHelper.Content(url));
            if (!string.IsNullOrEmpty(alternateText))
                builder.MergeAttribute("alt", alternateText);
            if (htmlAttributes != null)
                builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));

            // Render tag
            return MvcHtmlString.Create(builder.ToString(TagRenderMode.SelfClosing));
        }

        /// <summary>
        ///     Labels for.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="html">The HTML.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="labelText">The label text.</param>
        /// <param name="htmlAttributes">The HTML attributes.</param>
        /// <returns></returns>
        public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
                                                             Expression<Func<TModel, TValue>> expression,
                                                             string labelText, object htmlAttributes)
        {
            return LabelFor(html, expression, labelText, new RouteValueDictionary(htmlAttributes));
        }

        /// <summary>
        ///     Labels for.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TValue">The type of the value.</typeparam>
        /// <param name="html">The HTML.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="labelText">The label text.</param>
        /// <param name="htmlAttributes">The HTML attributes.</param>
        /// <returns></returns>
        public static MvcHtmlString LabelFor<TModel, TValue>(this HtmlHelper<TModel> html,
                                                             Expression<Func<TModel, TValue>> expression,
                                                             string labelText,
                                                             IDictionary<string, object> htmlAttributes)
        {
            ModelMetadata metadata = ModelMetadata.FromLambdaExpression(expression, html.ViewData);
            string htmlFieldName = ExpressionHelper.GetExpressionText(expression);
            labelText = labelText ??
                        metadata.DisplayName ?? metadata.PropertyName ?? htmlFieldName.Split('.').Last();
            if (String.IsNullOrEmpty(labelText))
                return MvcHtmlString.Empty;

            var tag = new TagBuilder("label");
            tag.MergeAttributes(htmlAttributes);
            tag.Attributes.Add("for", html.ViewContext.ViewData.TemplateInfo.GetFullHtmlFieldId(htmlFieldName));
            tag.SetInnerText(labelText);
            return MvcHtmlString.Create(tag.ToString(TagRenderMode.Normal));
        }

        /// <summary>
        ///     Renders a span tag around the given text.
        /// </summary>
        /// <param name="helper"></param>
        /// <param name="text"></param>
        /// <param name="id"></param>
        /// <param name="htmlAttributes"></param>
        /// <returns></returns>
        public static MvcHtmlString Span(this HtmlHelper helper, string text, string id, object htmlAttributes)
        {
            var builder = new TagBuilder("span");
            builder.GenerateId(id);
            builder.SetInnerText(text);
            if (htmlAttributes != null)
                builder.MergeAttributes(new RouteValueDictionary(htmlAttributes));
            return MvcHtmlString.Create(builder.ToString(TagRenderMode.Normal));
        }


        /// <summary>
        ///     Generates radio button group for a given Enum.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <returns></returns>
        public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                          Expression<Func<TModel, TProperty>> expression)
        {
            return RadioButtonForEnum(htmlHelper, expression, null);
        }

        /// <summary>
        ///     Generates radio button group for a given Enum.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="labelHtmlAttributes">The label HTML attributes.</param>
        /// <returns></returns>
        public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                          Expression<Func<TModel, TProperty>> expression,
                                                                          object labelHtmlAttributes)
        {
            return RadioButtonForEnum(htmlHelper, expression, labelHtmlAttributes, true);
        }

        /// <summary>
        ///     Generates radio button group for a given enumeration.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="labelHtmlAttributes">The label HTML attributes.</param>
        /// <param name="skipUnknown">
        ///     if set to <c>true</c> [skip unknown].
        /// </param>
        /// <returns></returns>
        public static MvcHtmlString RadioButtonForEnum<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                          Expression<Func<TModel, TProperty>> expression,
                                                                          object labelHtmlAttributes,
                                                                          bool skipUnknown)
        {
            ModelMetadata metaData = ModelMetadata.FromLambdaExpression(expression, htmlHelper.ViewData);
            IEnumerable<string> names = Enum.GetNames(metaData.ModelType).Where(n => n != "Unknown" || !skipUnknown);

            var sb = new StringBuilder();
            foreach (string name in names)
            {
                string id = string.Format("{0}_{1}_{2}", htmlHelper.ViewData.TemplateInfo.HtmlFieldPrefix,
                                          metaData.PropertyName, name);

                string radio = htmlHelper.RadioButtonFor(expression, name, new { id }).ToHtmlString();
                var labelTag = new TagBuilder("label");
                if (labelHtmlAttributes != null)
                    labelTag.MergeAttributes(new RouteValueDictionary(labelHtmlAttributes));
                labelTag.Attributes.Add("for", id);
                labelTag.SetInnerText(HttpUtility.HtmlEncode(name));
                string label = labelTag.ToString(TagRenderMode.Normal);

                sb.AppendFormat("{0} {1}", radio, label);
            }
            return MvcHtmlString.Create(sb.ToString());
        }

        /// <summary>
        ///     Generates a checkbox list for a given enumeration.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="multiSelectList">The multi select list.</param>
        /// <returns></returns>
        public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                       Expression<Func<TModel, TProperty[]>> expression,
                                                                       MultiSelectList multiSelectList)
        {
            return CheckBoxListFor(htmlHelper, expression, multiSelectList, null);
        }

        /// <summary>
        ///     Generates a checkbox list for a given enumeration.
        /// </summary>
        /// <typeparam name="TModel">The type of the model.</typeparam>
        /// <typeparam name="TProperty">The type of the property.</typeparam>
        /// <param name="htmlHelper">The HTML helper.</param>
        /// <param name="expression">The expression.</param>
        /// <param name="multiSelectList">The multi select list.</param>
        /// <param name="htmlAttributes">The HTML attributes.</param>
        /// <returns></returns>
        public static MvcHtmlString CheckBoxListFor<TModel, TProperty>(this HtmlHelper<TModel> htmlHelper,
                                                                       Expression<Func<TModel, TProperty[]>> expression,
                                                                       MultiSelectList multiSelectList,
                                                                       object htmlAttributes)
        {
            //Derive property name for checkbox name
            var body = expression.Body as MemberExpression;
            if (body != null)
            {
                string propertyName = body.Member.Name;

                //Get currently selected values from the ViewData model
                TProperty[] list = expression.Compile().Invoke(htmlHelper.ViewData.Model);
                var selectedValues = new List<string>();
                if (list != null)
                {
                    selectedValues = new List<TProperty>(list).ConvertAll(i => i.ToString());
                }

                var divTag = new TagBuilder("div");
                divTag.MergeAttributes(new RouteValueDictionary(htmlAttributes), true);

                var stringBuilder = new StringBuilder();
                int index = 0;
                foreach (SelectListItem item in multiSelectList)
                {
                    var div = new TagBuilder("div")
                        {
                            InnerHtml =
                                GetCheckbox(propertyName, index, item.Value, selectedValues.Contains(item.Value)) +
                                GetLabel(propertyName, index, item.Text)
                        };
                    stringBuilder.Append(div);
                    index++;
                }

                divTag.InnerHtml = stringBuilder.ToString();
                return MvcHtmlString.Create(divTag.ToString());
            }
            return null;
        }

        /// <summary>
        ///     Validation Summary that responds only to a given action. Use when several
        ///     summaries are on a single page.
        ///     Returns an unordered list (ul element) of validation messages that are in the
        ///     System.Web.Mvc.ModelStateDictionary object and optionally displays only model-level errors.
        /// </summary>
        /// <param name="html">The HTML helper instance that this method extends.</param>
        /// <param name="action">The action where this request was submitted to.</param>
        /// <returns></returns>
        public static MvcHtmlString ActionValidationSummary(this HtmlHelper html, string action)
        {
            return ActionValidationSummary(html, false, action);
        }

        /// <summary>
        ///     Validation Summary that responds only to a given action. Use when several
        ///     summaries are on a single page.
        ///     Returns an unordered list (ul element) of validation messages that are in the
        ///     System.Web.Mvc.ModelStateDictionary object and optionally displays only model-level errors.
        /// </summary>
        /// <param name="html">The HTML helper instance that this method extends.</param>
        /// <param name="excludePropertyErrors">true to have the summary display model-level errors only, or false to have the summary display all errors.</param>
        /// <param name="action">The action where this request was submitted to.</param>
        /// <returns></returns>
        public static MvcHtmlString ActionValidationSummary(this HtmlHelper html, bool excludePropertyErrors,
                                                            string action)
        {
            return ActionValidationSummary(html, excludePropertyErrors, null, action);
        }

        /// <summary>
        ///     Validation Summary that responds only to a given action. Use when several
        ///     summaries are on a single page.
        ///     Returns an unordered list (ul element) of validation messages that are in the
        ///     System.Web.Mvc.ModelStateDictionary object and optionally displays only model-level errors.
        /// </summary>
        /// <param name="html">The HTML helper instance that this method extends.</param>
        /// <param name="excludePropertyErrors">true to have the summary display model-level errors only, or false to have the summary display all errors.</param>
        /// <param name="message">The message to display with the validation summary.</param>
        /// <param name="action">The action where this request was submitted to.</param>
        /// <returns></returns>
        public static MvcHtmlString ActionValidationSummary(this HtmlHelper html, bool excludePropertyErrors,
                                                            string message, string action)
        {
            string currentAction = html.ViewContext.RouteData.Values["action"].ToString();
            return currentAction.ToLower() == action.ToLower()
                       ? html.ValidationSummary(excludePropertyErrors, message)
                       : new MvcHtmlString(string.Empty);
        }

        #region CheckBoxListFor helpers

        /// <summary>
        ///     Gets the checkbox.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="index">The index.</param>
        /// <param name="value">The value.</param>
        /// <param name="isChecked">
        ///     if set to <c>true</c> [is checked].
        /// </param>
        /// <returns></returns>
        private static string GetCheckbox(string propertyName, int index, string value, bool isChecked)
        {
            var checkbox = new TagBuilder("checkbox");
            checkbox.MergeAttribute("name", propertyName);
            checkbox.MergeAttribute("id", string.Format("{0}_{1}", propertyName, index));
            checkbox.MergeAttribute("value", value);
            if (isChecked)
            {
                checkbox.MergeAttribute("checked", "checked");
            }
            return checkbox.ToString();
        }

        /// <summary>
        ///     Gets the label.
        /// </summary>
        /// <param name="propertyName">Name of the property.</param>
        /// <param name="index">The index.</param>
        /// <param name="name">The name.</param>
        /// <returns></returns>
        private static string GetLabel(string propertyName, int index, string name)
        {
            var label = new TagBuilder("label");
            label.MergeAttribute("for", string.Format("{0}_{1}", propertyName, index));
            label.SetInnerText(name);

            return label.ToString();
        }

        #endregion
    }
}